Popular Searches
Popular Course Categories
Popular Courses

Creating grid-based layouts

Creating grid-based layouts

Flutter Lists & Collections

Creating Grid-Based Layouts in Flutter

Grid-based layouts are commonly used in Flutter applications when multiple items need to be displayed in rows and columns. Examples include product catalogs, image galleries, category menus, course cards, dashboards, food menus, and portfolio screens.

Flutter provides the GridView widget for creating scrollable two-dimensional layouts. A grid can use a fixed number of columns, a maximum tile width, or a custom grid delegate. Flutter GridView API Documentation

1. What Is a Grid-Based Layout?

A grid-based layout organizes UI elements into rows and columns. Each element occupies a grid cell or tile.

+-------------+-------------+
|   Item 1    |   Item 2    |
+-------------+-------------+
|   Item 3    |   Item 4    |
+-------------+-------------+
|   Item 5    |   Item 6    |
+-------------+-------------+

Unlike a traditional vertical ListView, a grid can display multiple items across the available width.

2. Common Uses of Grid-Based Layouts

  • Product catalogs
  • Photo galleries
  • Course listings
  • Category menus
  • Food delivery menus
  • Shopping applications
  • Dashboard cards
  • Portfolio projects
  • Social media image collections
  • Game item collections
  • App shortcuts

3. GridView in Flutter

GridView is a scrollable widget that arranges its children in a two-dimensional grid. Flutter provides several constructors for different grid requirements.

  • GridView()
  • GridView.builder()
  • GridView.count()
  • GridView.extent()
  • GridView.custom()

Flutter's documentation describes GridView as a scrollable two-dimensional array of widgets. GridView Class Documentation

4. Basic GridView Layout

The basic GridView constructor requires a gridDelegate that controls how the children are arranged.

GridView(
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  children: const [
    Card(
      child: Center(
        child: Text('Item 1'),
      ),
    ),
    Card(
      child: Center(
        child: Text('Item 2'),
      ),
    ),
    Card(
      child: Center(
        child: Text('Item 3'),
      ),
    ),
    Card(
      child: Center(
        child: Text('Item 4'),
      ),
    ),
  ],
)

5. GridView.count

GridView.count is one of the simplest ways to create a grid when you know how many columns you want.

GridView.count(
  crossAxisCount: 2,
  children: const [
    Card(child: Center(child: Text('Item 1'))),
    Card(child: Center(child: Text('Item 2'))),
    Card(child: Center(child: Text('Item 3'))),
    Card(child: Center(child: Text('Item 4'))),
  ],
)

The official Flutter grid recipe uses GridView.count to specify the number of columns in a grid. Flutter Create a Grid List

6. Understanding crossAxisCount

The crossAxisCount property specifies how many tiles appear across the cross axis.

GridView.count(
  crossAxisCount: 2,
  children: [
    ...
  ],
)

For a vertically scrolling grid:

  • crossAxisCount: 2 creates 2 columns.
  • crossAxisCount: 3 creates 3 columns.
  • crossAxisCount: 4 creates 4 columns.

When the grid scrolls horizontally, the cross axis is vertical, so the same property determines the number of rows. SliverGridDelegateWithFixedCrossAxisCount Documentation

7. Complete GridView.count Example

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Grid Layout'),
        ),
        body: GridView.count(
          padding: const EdgeInsets.all(16),
          crossAxisCount: 2,
          mainAxisSpacing: 12,
          crossAxisSpacing: 12,
          children: const [
            Card(
              child: Center(
                child: Text('Laptop'),
              ),
            ),
            Card(
              child: Center(
                child: Text('Mobile'),
              ),
            ),
            Card(
              child: Center(
                child: Text('Tablet'),
              ),
            ),
            Card(
              child: Center(
                child: Text('Headphones'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

8. GridView.builder

When grid items are generated dynamically, GridView.builder is generally more suitable. It builds children on demand and is useful for large collections.

GridView.builder(
  itemCount: items.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text(items[index]),
      ),
    );
  },
)

Flutter's API documentation recommends GridView.builder for large or potentially infinite collections because grid children can be created on demand. GridView.builder API Documentation

9. Why Use GridView.builder?

  • Useful for dynamically generated data.
  • Suitable for large collections.
  • Creates grid children on demand.
  • Works well with API and database data.
  • Useful for product catalogs and image galleries.
  • Reduces the need to create every grid widget upfront.

10. Basic GridView.builder Syntax

GridView.builder(
  itemCount: items.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return YourWidget(
      data: items[index],
    );
  },
)

11. Understanding gridDelegate

The gridDelegate controls the layout of the grid children. It determines information such as the number of tiles, tile size, spacing, and aspect ratio. Flutter gridDelegate Documentation

The two commonly used delegates are:

  • SliverGridDelegateWithFixedCrossAxisCount
  • SliverGridDelegateWithMaxCrossAxisExtent

12. SliverGridDelegateWithFixedCrossAxisCount

This delegate creates a grid with a fixed number of tiles across the cross axis.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
)

For a vertical grid, this means a fixed number of columns. For a horizontal grid, it means a fixed number of rows. Fixed Cross Axis Count Documentation

13. mainAxisSpacing

mainAxisSpacing controls the space between tiles along the main axis.

GridView.builder(
  itemCount: 20,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 16,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text('Item $index'),
      ),
    );
  },
)

14. crossAxisSpacing

crossAxisSpacing controls the space between tiles along the cross axis.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  crossAxisSpacing: 16,
)

15. Using Both Spacing Properties

GridView.builder(
  itemCount: 20,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text('Item $index'),
      ),
    );
  },
)

16. childAspectRatio

childAspectRatio controls the ratio between the cross-axis extent and the main-axis extent of each tile.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  childAspectRatio: 1,
)

A value of 1 generally produces square tiles.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  childAspectRatio: 1.5,
)

A higher value makes the tile relatively wider, while a lower value makes it relatively taller.

17. mainAxisExtent

mainAxisExtent lets you directly specify the extent of each tile along the main axis.

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
  mainAxisExtent: 180,
)

This can be useful when every grid card needs a predictable height.

18. GridView.extent

GridView.extent allows you to specify the maximum cross-axis extent of a tile instead of directly specifying the number of columns.

GridView.extent(
  maxCrossAxisExtent: 200,
  padding: const EdgeInsets.all(12),
  mainAxisSpacing: 12,
  crossAxisSpacing: 12,
  children: const [
    Card(child: Center(child: Text('Item 1'))),
    Card(child: Center(child: Text('Item 2'))),
    Card(child: Center(child: Text('Item 3'))),
    Card(child: Center(child: Text('Item 4'))),
  ],
)

This approach is useful for layouts where the number of columns should adapt to the available width. Flutter's layout documentation demonstrates GridView.extent for adaptive grid tile widths. Flutter Layout Documentation

19. SliverGridDelegateWithMaxCrossAxisExtent

SliverGridDelegateWithMaxCrossAxisExtent creates tiles with a maximum cross-axis extent. Flutter can determine how many tiles fit based on the available space and the maximum extent.

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 250,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
    childAspectRatio: 0.8,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

This is particularly useful for responsive layouts because the number of columns can adapt to the available width. Max Cross Axis Extent Documentation

20. Fixed Columns vs Adaptive Width

ApproachUse When
SliverGridDelegateWithFixedCrossAxisCountYou want a specific number of columns.
SliverGridDelegateWithMaxCrossAxisExtentYou want tile width to adapt to available space.
GridView.countYou want a simple fixed-count grid.
GridView.extentYou want a simple adaptive-width grid.

21. Creating a Product Grid

class Product {
  final String name;
  final double price;

  Product({
    required this.name,
    required this.price,
  });
}

final products = [
  Product(name: 'Laptop', price: 55000),
  Product(name: 'Mobile', price: 30000),
  Product(name: 'Tablet', price: 22000),
  Product(name: 'Keyboard', price: 2500),
  Product(name: 'Mouse', price: 1200),
  Product(name: 'Headphones', price: 5000),
];

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
    childAspectRatio: 0.8,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return Card(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          const Icon(
            Icons.shopping_bag,
            size: 50,
          ),
          const SizedBox(height: 10),
          Text(
            product.name,
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 6),
          Text('₹${product.price}'),
        ],
      ),
    );
  },
)

22. Creating a Category Grid

final categories = [
  'Electronics',
  'Fashion',
  'Shoes',
  'Books',
  'Furniture',
  'Beauty',
  'Sports',
  'Grocery',
];

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: categories.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text(
          categories[index],
          textAlign: TextAlign.center,
        ),
      ),
    );
  },
)

23. Creating a Dashboard Grid

Grid layouts are useful for dashboards where each card represents a different statistic or application section.

final dashboardItems = [
  {
    'title': 'Users',
    'value': '12,450',
    'icon': Icons.people,
  },
  {
    'title': 'Orders',
    'value': '3,240',
    'icon': Icons.shopping_cart,
  },
  {
    'title': 'Revenue',
    'value': '₹8,45,000',
    'icon': Icons.currency_rupee,
  },
  {
    'title': 'Pending',
    'value': '128',
    'icon': Icons.pending,
  },
];

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: dashboardItems.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
    childAspectRatio: 1.2,
  ),
  itemBuilder: (context, index) {
    final item = dashboardItems[index];

    return Card(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Icon(
            item['icon'] as IconData,
            size: 40,
          ),
          const SizedBox(height: 10),
          Text(item['title'] as String),
          const SizedBox(height: 5),
          Text(
            item['value'] as String,
            style: const TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  },
)

24. Creating an Image Gallery

A grid is a natural choice for displaying multiple images.

final imageUrls = [
  'https://example.com/image1.jpg',
  'https://example.com/image2.jpg',
  'https://example.com/image3.jpg',
  'https://example.com/image4.jpg',
];

GridView.builder(
  padding: const EdgeInsets.all(8),
  itemCount: imageUrls.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 3,
    mainAxisSpacing: 8,
    crossAxisSpacing: 8,
  ),
  itemBuilder: (context, index) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(10),
      child: Image.network(
        imageUrls[index],
        fit: BoxFit.cover,
      ),
    );
  },
)

25. Creating Grid Tiles with GridTile

GridTile can be used when a grid item needs a structured visual layout with an optional header or footer.

GridView.count(
  crossAxisCount: 2,
  children: [
    GridTile(
      header: const GridTileBar(
        title: Text('Laptop'),
      ),
      footer: const GridTileBar(
        title: Text('₹55,000'),
      ),
      child: Container(
        color: Colors.grey,
        child: const Icon(
          Icons.laptop,
          size: 60,
        ),
      ),
    ),
    GridTile(
      header: const GridTileBar(
        title: Text('Mobile'),
      ),
      footer: const GridTileBar(
        title: Text('₹30,000'),
      ),
      child: Container(
        color: Colors.grey,
        child: const Icon(
          Icons.phone_android,
          size: 60,
        ),
      ),
    ),
  ],
)

26. Adding Padding Around a Grid

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

Padding creates space between the grid boundary and its contents.

27. Responsive Grid Layout

A responsive grid changes its number of columns depending on available screen width. This is important when an application must work across phones, tablets, and desktop screens.

LayoutBuilder(
  builder: (context, constraints) {
    int columns;

    if (constraints.maxWidth >= 1200) {
      columns = 5;
    } else if (constraints.maxWidth >= 900) {
      columns = 4;
    } else if (constraints.maxWidth >= 600) {
      columns = 3;
    } else {
      columns = 2;
    }

    return GridView.builder(
      itemCount: products.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: columns,
        mainAxisSpacing: 16,
        crossAxisSpacing: 16,
        childAspectRatio: 0.8,
      ),
      itemBuilder: (context, index) {
        return ProductCard(
          product: products[index],
        );
      },
    );
  },
)

28. Responsive Grid with OrientationBuilder

OrientationBuilder can be used to change the grid layout when the device changes between portrait and landscape orientation.

OrientationBuilder(
  builder: (context, orientation) {
    final columns = orientation == Orientation.portrait
        ? 2
        : 3;

    return GridView.count(
      crossAxisCount: columns,
      children: List.generate(
        12,
        (index) {
          return Card(
            child: Center(
              child: Text('Item $index'),
            ),
          );
        },
      ),
    );
  },
)

Flutter's official orientation recipe demonstrates changing crossAxisCount based on the device orientation. Flutter Orientation-Based Layout Documentation

29. Responsive Grid with MaxCrossAxisExtent

For many responsive layouts, SliverGridDelegateWithMaxCrossAxisExtent can avoid manually calculating breakpoints.

GridView.builder(
  padding: const EdgeInsets.all(16),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 260,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
    childAspectRatio: 0.8,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

The delegate chooses a tile width that is as large as possible while staying at or below the specified maximum cross-axis extent and fitting evenly across the grid. Max Cross Axis Extent API

30. Grid Layout Inside a Column

When a GridView is placed inside a Column, it needs an appropriate constraint. Expanded is commonly used when the grid should occupy the remaining available space.

Column(
  children: [
    const Padding(
      padding: EdgeInsets.all(16),
      child: Text(
        'Products',
        style: TextStyle(
          fontSize: 24,
          fontWeight: FontWeight.bold,
        ),
      ),
    ),
    Expanded(
      child: GridView.builder(
        itemCount: products.length,
        gridDelegate:
            const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 12,
          crossAxisSpacing: 12,
        ),
        itemBuilder: (context, index) {
          return ProductCard(
            product: products[index],
          );
        },
      ),
    ),
  ],
)

31. GridView with shrinkWrap

shrinkWrap: true allows the grid to size itself based on its contents along the scroll direction. It can be useful when embedding a grid inside another scrollable layout, but it should not be enabled unnecessarily.

GridView.builder(
  shrinkWrap: true,
  physics: const NeverScrollableScrollPhysics(),
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    return ProductCard(
      product: products[index],
    );
  },
)

32. Horizontal Grid Layout

GridView can also scroll horizontally.

GridView.builder(
  scrollDirection: Axis.horizontal,
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
  ),
  itemBuilder: (context, index) {
    return Card(
      child: Center(
        child: Text(products[index].name),
      ),
    );
  },
)

When scrollDirection is horizontal, crossAxisCount represents the number of rows rather than columns. Fixed Cross Axis Count Documentation

33. Grid Layout with Cards

class ProductCard extends StatelessWidget {
  final Product product;

  const ProductCard({
    super.key,
    required this.product,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 3,
      clipBehavior: Clip.antiAlias,
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Icon(
              Icons.shopping_bag,
              size: 50,
            ),
            const SizedBox(height: 12),
            Text(
              product.name,
              textAlign: TextAlign.center,
            ),
            const SizedBox(height: 6),
            Text(
              '₹${product.price}',
              style: const TextStyle(
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

34. Grid Items with onTap

Grid items can respond to user interaction with InkWell or GestureDetector.

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return InkWell(
      onTap: () {
        print('Selected: ${product.name}');
      },
      child: ProductCard(
        product: product,
      ),
    );
  },
)

35. Navigating from Grid Item to Detail Screen

GridView.builder(
  itemCount: products.length,
  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2,
  ),
  itemBuilder: (context, index) {
    final product = products[index];

    return InkWell(
      onTap: () {
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) {
              return ProductDetailsScreen(
                product: product,
              );
            },
          ),
        );
      },
      child: ProductCard(
        product: product,
      ),
    );
  },
)

36. Dynamic Grid Layout

Grid-based layouts can display data that changes during runtime.

class DynamicGridScreen extends StatefulWidget {
  const DynamicGridScreen({super.key});

  @override
  State createState() => _DynamicGridScreenState();
}

class _DynamicGridScreenState extends State {
  final List items = [
    'Item 1',
    'Item 2',
    'Item 3',
    'Item 4',
  ];

  void addItem() {
    setState(() {
      items.add(
        'Item ${items.length + 1}',
      );
    });
  }

  void removeItem(int index) {
    setState(() {
      items.removeAt(index);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dynamic Grid'),
        actions: [
          IconButton(
            onPressed: addItem,
            icon: const Icon(Icons.add),
          ),
        ],
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: items.length,
        gridDelegate:
            const SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: 2,
          mainAxisSpacing: 12,
          crossAxisSpacing: 12,
        ),
        itemBuilder: (context, index) {
          return Card(
            child: Stack(
              children: [
                Center(
                  child: Text(items[index]),
                ),
                Positioned(
                  top: 2,
                  right: 2,
                  child: IconButton(
                    onPressed: () {
                      removeItem(index);
                    },
                    icon: const Icon(Icons.delete),
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

37. Creating a Grid from List.generate

For small static or predictable collections, List.generate can be used with GridView.count.

GridView.count(
  crossAxisCount: 3,
  children: List.generate(
    12,
    (index) {
      return Card(
        child: Center(
          child: Text(
            'Item $index',
          ),
        ),
      );
    },
  ),
)

The official Flutter grid recipe uses List.generate to create a collection of grid children. Flutter Grid List Recipe

38. Creating a Responsive Dashboard

LayoutBuilder(
  builder: (context, constraints) {
    final width = constraints.maxWidth;

    final columns = width >= 1200
        ? 4
        : width >= 800
            ? 3
            : 2;

    return GridView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: dashboardItems.length,
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: columns,
        crossAxisSpacing: 16,
        mainAxisSpacing: 16,
        childAspectRatio: 1.4,
      ),
      itemBuilder: (context, index) {
        return DashboardCard(
          item: dashboardItems[index],
        );
      },
    );
  },
)

39. GridView and CustomScrollView

For simple grid screens, GridView is usually enough. For complex pages containing multiple scrollable sections, Flutter allows the grid to be represented as a SliverGrid inside a CustomScrollView.

CustomScrollView(
  slivers: [
    const SliverAppBar(
      title: Text('Products'),
      floating: true,
    ),
    SliverGrid.builder(
      gridDelegate:
          const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemCount: products.length,
      itemBuilder: (context, index) {
        return ProductCard(
          product: products[index],
        );
      },
    ),
  ],
)

Flutter documents a GridView as essentially a CustomScrollView containing a single SliverGrid. This makes it possible to move to CustomScrollView when a page needs combinations such as a SliverAppBar, SliverList, and SliverGrid. Flutter GridView and CustomScrollView Documentation

40. GridView Constructor Comparison

ConstructorBest Use
GridView()Explicit children with a custom grid delegate.
GridView.builder()Large or dynamically generated collections.
GridView.count()Simple grid with a fixed number of columns or rows.
GridView.extent()Grid based on maximum tile extent.
GridView.custom()Custom child and grid delegates.

41. Important Grid Layout Properties

PropertyDescription
crossAxisCountNumber of tiles in the cross axis.
maxCrossAxisExtentMaximum cross-axis extent of a tile.
mainAxisSpacingSpace between tiles along the main axis.
crossAxisSpacingSpace between tiles along the cross axis.
childAspectRatioRatio of cross-axis extent to main-axis extent.
mainAxisExtentExplicit main-axis extent for each tile.
paddingSpace around the grid.
scrollDirectionControls vertical or horizontal scrolling.
reverseReverses the scroll direction.
shrinkWrapSizes the grid according to its contents when required.
physicsControls scrolling behavior.
controllerControls and monitors scrolling.

42. Common Mistakes in Grid-Based Layouts

Mistake 1: Forgetting gridDelegate

GridView.builder(
  itemCount: products.length,
  itemBuilder: (context, index) {
    return Text(products[index].name);
  },
)

A GridView.builder requires a gridDelegate.

Mistake 2: Too Many Columns

Using too many columns can make each tile too narrow, especially on mobile screens.

For example:

crossAxisCount: 6

may not be appropriate for a small phone screen. Choose the number of columns based on available space and content.

Mistake 3: Incorrect Aspect Ratio

An inappropriate childAspectRatio can cause cards to become too short or too tall.

Mistake 4: Unnecessary shrinkWrap

Do not use shrinkWrap: true automatically. Use it only when the surrounding layout requires the grid to size itself to its contents.

Mistake 5: Nested Scrollables

Avoid unnecessarily nesting a scrollable GridView inside another scrollable widget. When multiple scrollable sections are required, consider using CustomScrollView and slivers.

Mistake 6: Using Explicit Children for Huge Data

For large dynamic collections, use GridView.builder instead of constructing a huge explicit list of grid widgets.

43. Grid Layout Performance Best Practices

  • Use GridView.builder for large or dynamic datasets.
  • Provide itemCount when the number of items is known.
  • Keep grid item widgets reasonably lightweight.
  • Avoid unnecessary widget rebuilds.
  • Use stable keys when grid item identity matters.
  • Use appropriate image sizes in image-heavy grids.
  • Use pagination for very large API datasets.
  • Avoid unnecessary shrinkWrap.
  • Avoid unnecessary nested scrolling.
  • Use an appropriate grid delegate for the required layout.
  • Test the grid on different screen sizes.

44. When to Use Fixed Column Grids

Use SliverGridDelegateWithFixedCrossAxisCount when the design requires a specific number of columns.

Example:

const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2,
)

This is useful for designs such as:

  • Two-column product cards.
  • Two-column category menus.
  • Three-column photo galleries.
  • Four-column desktop dashboards.

45. When to Use Maximum Tile Width

Use SliverGridDelegateWithMaxCrossAxisExtent when the tile width should adapt according to available space.

const SliverGridDelegateWithMaxCrossAxisExtent(
  maxCrossAxisExtent: 250,
)

This approach is useful for responsive applications that need to work across phones, tablets, and desktop screens without manually specifying every breakpoint.

46. Complete Responsive Product Grid Example

import 'package:flutter/material.dart';

class Product {
  final int id;
  final String name;
  final double price;

  Product({
    required this.id,
    required this.name,
    required this.price,
  });
}

class ProductGridScreen extends StatelessWidget {
  ProductGridScreen({super.key});

  final List products = [
    Product(
      id: 1,
      name: 'Laptop',
      price: 55000,
    ),
    Product(
      id: 2,
      name: 'Mobile',
      price: 30000,
    ),
    Product(
      id: 3,
      name: 'Tablet',
      price: 22000,
    ),
    Product(
      id: 4,
      name: 'Keyboard',
      price: 2500,
    ),
    Product(
      id: 5,
      name: 'Mouse',
      price: 1200,
    ),
    Product(
      id: 6,
      name: 'Headphones',
      price: 5000,
    ),
    Product(
      id: 7,
      name: 'Monitor',
      price: 18000,
    ),
    Product(
      id: 8,
      name: 'Smart Watch',
      price: 8000,
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Product Grid'),
      ),
      body: GridView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: products.length,
        gridDelegate:
            const SliverGridDelegateWithMaxCrossAxisExtent(
          maxCrossAxisExtent: 250,
          mainAxisSpacing: 16,
          crossAxisSpacing: 16,
          childAspectRatio: 0.8,
        ),
        itemBuilder: (context, index) {
          final product = products[index];

          return Card(
            elevation: 3,
            child: Padding(
              padding: const EdgeInsets.all(12),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(
                    Icons.shopping_bag,
                    size: 55,
                  ),
                  const SizedBox(height: 12),
                  Text(
                    product.name,
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 8),
                  Text(
                    '₹${product.price}',
                    style: const TextStyle(
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ],
              ),
            ),
          );
        },
      ),
    );
  }
}

void main() {
  runApp(
    MaterialApp(
      debugShowCheckedModeBanner: false,
      home: ProductGridScreen(),
    ),
  );
}

47. How Grid-Based Layouts Work

  1. Create a collection of data or widgets.
  2. Select the appropriate GridView constructor.
  3. Choose a grid delegate.
  4. Set the number of columns or maximum tile width.
  5. Configure spacing between tiles.
  6. Configure tile dimensions using aspect ratio or main-axis extent.
  7. Add padding around the grid if required.
  8. Use a builder for large or dynamic collections.
  9. Make the layout responsive when supporting multiple screen sizes.

48. GridView and ListView Comparison

GridViewListView
Two-dimensional layout.One-dimensional layout.
Displays rows and columns.Displays items linearly.
Good for products and galleries.Good for messages and settings.
Supports multiple columns.Normally displays one item per line.
Uses grid delegates.Uses linear list layout.

49. Practice Exercises

  1. Create a two-column product grid.
  2. Create a three-column category grid.
  3. Create a responsive image gallery.
  4. Create a dashboard with four statistic cards.
  5. Build a course listing using GridView.builder.
  6. Use childAspectRatio to create different card shapes.
  7. Create a grid that adapts using SliverGridDelegateWithMaxCrossAxisExtent.
  8. Create a horizontal grid with two rows.
  9. Add click functionality to every grid item.
  10. Navigate to a detail screen when a grid item is tapped.
  11. Add and remove items dynamically.
  12. Load grid items from an API.
  13. Add loading, error, and empty states.
  14. Create a responsive grid that changes columns based on orientation.

50. Quick Revision

ConceptKey Point
GridViewCreates a scrollable two-dimensional layout.
GridView.countCreates a grid with a fixed number of cross-axis tiles.
GridView.extentCreates a grid based on maximum tile extent.
GridView.builderCreates grid items on demand.
gridDelegateControls grid tile layout.
crossAxisCountControls the number of columns in a vertical grid.
maxCrossAxisExtentControls maximum tile width in a vertical grid.
mainAxisSpacingControls spacing along the main axis.
crossAxisSpacingControls spacing along the cross axis.
childAspectRatioControls tile width-to-height proportion.
mainAxisExtentControls tile extent along the main axis.
LayoutBuilderCan be used to create responsive column counts.
OrientationBuilderCan adapt the grid based on portrait or landscape orientation.
CustomScrollViewUseful for complex pages containing multiple sliver sections.

51. Key Takeaways

  • Grid-based layouts are ideal for displaying multiple items in rows and columns.
  • GridView is Flutter's primary widget for scrollable grid layouts.
  • GridView.count is convenient when the number of columns is known.
  • GridView.extent is useful when tile width should adapt to available space.
  • GridView.builder is appropriate for large and dynamically generated collections.
  • SliverGridDelegateWithFixedCrossAxisCount creates grids with a fixed number of cross-axis tiles.
  • SliverGridDelegateWithMaxCrossAxisExtent is useful for adaptive grid widths.
  • mainAxisSpacing and crossAxisSpacing control tile spacing.
  • childAspectRatio controls the relative dimensions of each tile.
  • Responsive grids can adapt using LayoutBuilder, OrientationBuilder, or maximum tile extents.
  • For complex scrolling screens, CustomScrollView with SliverGrid provides additional flexibility.

52. Official Flutter Resources

53. Learn Flutter with JustAcademy

For structured Flutter training, practical learning, and course guidance, visit these resources:

whatsapp